{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import scipy\n",
    "from sklearn import metrics\n",
    "from numpy import random\n",
    "import tensorflow as tf\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from sklearn import cluster, preprocessing, decomposition\n",
    "from scipy.sparse import csgraph\n",
    "from tensorflow.keras import layers\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 将每个像素点作为graph中的结点，然后随机采样400个结点，每个结点连接最近的8个结点，生成对应的graph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_grid_graph(m, k=8, num_samples=400):\n",
    "    #生成m*m的图像中每个像素点的坐标\n",
    "    M = m**2\n",
    "    x = np.linspace(0, 1, m, dtype=np.float32)\n",
    "    y = np.linspace(0, 1, m, dtype=np.float32)\n",
    "    xx, yy = np.meshgrid(x, y)\n",
    "    z = np.empty((M, 2), np.float32)\n",
    "    z[:, 0] = xx.reshape(M)\n",
    "    z[:, 1] = yy.reshape(M)\n",
    "    \n",
    "    # 随机选择部分结点\n",
    "    sample_inds = random.choice(range(M), num_samples, False)\n",
    "    sample_inds.sort()\n",
    "    z_sample = z[sample_inds, :]\n",
    "    \n",
    "    #计算像素点之间的距离\n",
    "    dist = metrics.pairwise_distances(\n",
    "        z_sample, metric='euclidean')\n",
    "    #取前k个最近邻结点 k-NN graph.\n",
    "    idx = np.argsort(dist)[:, 1:k+1] #前k个最近邻结点的idx\n",
    "    dist.sort()\n",
    "    dist = dist[:, 1:k+1] #前k个最近邻结点的距离\n",
    "    \n",
    "    # 根据距离计算边的权重\n",
    "    sigma2 = np.mean(dist[:, -1])**2\n",
    "    dist = np.exp(- dist**2 / sigma2)\n",
    "\n",
    "    # 权重矩阵\n",
    "    I = np.arange(0, num_samples).repeat(k)\n",
    "    J = idx.reshape(num_samples*k)\n",
    "    V = dist.reshape(num_samples*k)\n",
    "    W = scipy.sparse.coo_matrix((V, (I, J)), shape=(num_samples, num_samples))\n",
    "\n",
    "    # 去除自连接\n",
    "    W.setdiag(0)\n",
    "\n",
    "    # 数值计算问题，引起的矩阵不对称，修正这一问题\n",
    "    bigger = W.T > W\n",
    "    W = W - W.multiply(bigger) + W.T.multiply(bigger)\n",
    "    \n",
    "    return sample_inds, W"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "img_width = 28\n",
    "sample_inds, W_sample = generate_grid_graph(28)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 读取MNIST数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "x_train_sample = x_train.reshape(x_train.shape[0], -1, 1).astype(np.float32)[:, sample_inds]/255.0\n",
    "x_test_sample = x_test.reshape(x_test.shape[0], -1, 1).astype(np.float32)[:, sample_inds]/255.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 可视化采样后的图像"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "f, axes = plt.subplots(1, 2)\n",
    "minist_img = x_train[0]\n",
    "mask = np.zeros_like(minist_img).reshape(-1)\n",
    "mask += 1\n",
    "mask[sample_inds] = 0\n",
    "minist_img = minist_img.reshape(img_width, img_width)\n",
    "mask = mask.astype(np.bool).reshape(img_width, img_width)\n",
    "\n",
    "fig1 = sns.heatmap(minist_img, cmap=\"YlGnBu\", ax=axes[0], xticklabels=False, yticklabels=False, cbar=False, square=True).set_title(\"original image\")\n",
    "fig2 = sns.heatmap(minist_img, mask=mask, cmap=\"YlGnBu\", ax=axes[1], xticklabels=False, yticklabels=False, cbar=False, square=True).set_title(\"subsample image\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 空域卷积"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SpatialConvolution(layers.Layer):\n",
    "    def __init__(self, graph, fin, fout, w_init):\n",
    "        super(SpatialConvolution, self).__init__()\n",
    "        self.fin = fin\n",
    "        self.fout = fout\n",
    "        self.num_node = graph.shape[0]\n",
    "        w_init = w_init(stddev=np.sqrt(2.0/self.fin))\n",
    "        nonzero_inds = np.stack(np.nonzero(graph), axis=1) \n",
    "        weights = tf.Variable(initial_value=w_init(shape=(fin*fout*len(nonzero_inds), ),\n",
    "                                                  dtype='float32'),\n",
    "                             trainable=True)\n",
    "        indices = []\n",
    "        for i in range(fin):\n",
    "            for j in range(fout):\n",
    "                fin_ind = np.repeat([[i]], repeats=len(nonzero_inds), axis=0)\n",
    "                fout_ind = np.repeat([[j]], repeats=len(nonzero_inds), axis=0)\n",
    "                new_nonzero_inds = np.concatenate([fout_ind, nonzero_inds, fin_ind], axis=1)\n",
    "                indices.append(new_nonzero_inds)\n",
    "        indices = np.concatenate(indices, axis=0)\n",
    "\n",
    "        F = tf.sparse.SparseTensor(indices, tf.identity(weights), (fout, self.num_node, self.num_node, fin))\n",
    "        self.F_tran_reshape = tf.sparse.reorder(tf.sparse.reshape(F, shape=(fout*self.num_node, self.num_node*fin)))\n",
    "    \n",
    "    def call(self, inputs):\n",
    "        inputs_reshape = tf.transpose(tf.reshape(inputs, shape=(-1, self.fin*self.num_node)))\n",
    "        c1 = tf.sparse.sparse_dense_matmul(self.F_tran_reshape, inputs_reshape)\n",
    "        output = tf.nn.relu(c1)\n",
    "        output = tf.reshape(tf.transpose(output), (-1, self.num_node, self.fout))\n",
    "        return output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### avg pooling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AvgPooling(layers.Layer):\n",
    "    def __init__(self, graph, fout):\n",
    "        super(AvgPooling, self).__init__()\n",
    "        num_node = graph.shape[0]\n",
    "        cluster_ids = cluster.spectral_clustering(graph, n_clusters=fout)\n",
    "        cluster_ids2node_ids = [[] for i in range(fout)]\n",
    "        for node_id, cluster_id in enumerate(cluster_ids):\n",
    "            cluster_ids2node_ids[cluster_id].append(node_id)\n",
    "\n",
    "        new_graph = np.zeros((fout, fout), dtype=np.float32)\n",
    "        for cluster_id_i, nodes_ids_i in enumerate(cluster_ids2node_ids):\n",
    "            for cluster_id_j, nodes_ids_j in enumerate(cluster_ids2node_ids):\n",
    "                for node_id_i in nodes_ids_i:\n",
    "                    for node_id_j in nodes_ids_j:\n",
    "                        new_graph[cluster_id_i, cluster_id_j] += graph[node_id_i, node_id_j]\n",
    "        self.graph = preprocessing.normalize(new_graph, norm='l1', axis=1)\n",
    "\n",
    "        self.node2cluster_matrix = np.zeros((num_node, fout), dtype=np.float32)\n",
    "        for cluster_id, node_ids in enumerate(cluster_ids2node_ids):\n",
    "            self.node2cluster_matrix[node_ids, cluster_id] = 1.0/len(node_ids)\n",
    "            \n",
    "    def get_graph(self):\n",
    "        return self.graph\n",
    "    \n",
    "    def call(self, inputs):\n",
    "        output = tf.transpose(tf.tensordot(inputs, self.node2cluster_matrix, axes=[[1], [0]]), perm=[0, 2, 1])\n",
    "        return output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 频域卷积"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SpectralConvolution(layers.Layer):\n",
    "    def __init__(self, graph, fin, fout, n_component, w_init):\n",
    "        super(SpectralConvolution, self).__init__()\n",
    "        self.num_node = graph.shape[0]\n",
    "        self.fin = fin\n",
    "        self.fout = fout\n",
    "        self.n_component = n_component\n",
    "        w_init = w_init(stddev=np.sqrt(2.0/self.fin))\n",
    "        laplacian_graph = csgraph.laplacian(graph, normed=False)\n",
    "        eigen_value, eigen_vector = np.linalg.eig(laplacian_graph.todense())\n",
    "        idx = eigen_value.argsort()[::-1]   \n",
    "        eigen_value = eigen_value[idx][-self.n_component:]\n",
    "        eigen_vector = eigen_vector[:,idx][:, -self.n_component:]\n",
    "        \n",
    "        self.u = eigen_vector.T\n",
    "\n",
    "        weights = tf.Variable(initial_value=w_init(shape=(fin*fout*self.n_component, ),\n",
    "                                                  dtype='float32'),\n",
    "                             trainable=True)\n",
    "        indices = []\n",
    "        for i in range(fin):\n",
    "            for j in range(fout):\n",
    "                fin_ind = np.repeat([[i]], repeats=self.n_component, axis=0)\n",
    "                fout_ind = np.repeat([[i]], repeats=self.n_component, axis=0)\n",
    "                nonzero_inds = np.array([[k, k] for k in range(self.n_component)], dtype=np.int32)\n",
    "                new_nonzero_inds = np.concatenate([fout_ind, nonzero_inds, fin_ind], axis=1)\n",
    "                indices.append(new_nonzero_inds)\n",
    "        indices = np.concatenate(indices, axis=0)\n",
    "\n",
    "        F = tf.sparse.SparseTensor(indices, tf.identity(weights), (fout, self.n_component, self.n_component, fin))\n",
    "        self.F_tran_reshape = tf.sparse.reorder(tf.sparse.reshape(F, shape=(fout*self.n_component, self.n_component*fin)))\n",
    "    \n",
    "    def call(self, inputs):\n",
    "        inputs_spectral = tf.tensordot(inputs, self.u, axes=[[1], [1]])\n",
    "        inputs_spectral_reshape = tf.transpose(tf.reshape(inputs_spectral, shape=(-1, self.fin*self.n_component)))\n",
    "        F_output = tf.transpose(tf.sparse.sparse_dense_matmul(self.F_tran_reshape, inputs_spectral_reshape))\n",
    "        F_output = tf.reshape(F_output, (-1, self.fout, self.n_component))\n",
    "        c1 = tf.transpose(tf.tensordot(F_output, self.u, axes=[[2], [0]]), perm=[0, 2, 1])\n",
    "        output = tf.nn.relu(c1)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 空域图卷积模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SpatialGCN(tf.keras.Model):\n",
    "    def __init__(self, graph, num_classes=10):\n",
    "        super(SpatialGCN, self).__init__()\n",
    "        w_init = tf.random_normal_initializer\n",
    "        self.lrf_3200 = SpatialConvolution(graph, 1, 8, w_init)\n",
    "        self.ap_800 = AvgPooling(graph, 100)\n",
    "        graph_100 = self.ap_800.get_graph()\n",
    "        self.lrf_800 = SpatialConvolution(graph_100, 8, 8, w_init)\n",
    "        self.ap_400 = AvgPooling(graph_100, 50)\n",
    "        self.fc = layers.Dense(num_classes)\n",
    "        \n",
    "    def call(self, inputs):\n",
    "        lrf_3200_output = self.lrf_3200(inputs)\n",
    "        ap_800_output = self.ap_800(lrf_3200_output)\n",
    "        lrf_800_output = self.lrf_800(ap_800_output)\n",
    "        ap_400_output = self.ap_400(lrf_800_output)\n",
    "        ap_400_output_reshape = tf.reshape(ap_400_output, (-1, 400))\n",
    "        logits = self.fc(ap_400_output_reshape)\n",
    "        predictions = tf.nn.softmax(logits)\n",
    "        return predictions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 频域图卷积模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SpectralGCN(tf.keras.Model):\n",
    "    def __init__(self, graph, num_classes=10):\n",
    "        super(SpectralGCN, self).__init__()\n",
    "        w_init = tf.random_normal_initializer\n",
    "        self.sp_1600 = SpectralConvolution(graph, 1, 4, 300, w_init)\n",
    "        self.fc = layers.Dense(num_classes)\n",
    "        \n",
    "    def call(self, inputs):\n",
    "        sp_1600_output = self.sp_1600(inputs)\n",
    "        sp_1600_output_reshape = tf.reshape(sp_1600_output, (-1, 1600))\n",
    "        logits = self.fc(sp_1600_output_reshape)\n",
    "        predictions = tf.nn.softmax(logits)\n",
    "        return predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)\n",
    "loss_fn = tf.keras.losses.SparseCategoricalCrossentropy()\n",
    "metrics = [tf.keras.metrics.SparseCategoricalAccuracy()]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.7/site-packages/sklearn/utils/validation.py:855: UserWarning: Array is not symmetric, and will be converted to symmetric by average with its transpose.\n",
      "  warnings.warn(\"Array is not symmetric, and will be converted \"\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 60000 samples, validate on 10000 samples\n",
      "Epoch 1/50\n",
      "60000/60000 [==============================] - 4s 66us/sample - loss: 0.4548 - sparse_categorical_accuracy: 0.8631 - val_loss: 0.3034 - val_sparse_categorical_accuracy: 0.9140\n",
      "Epoch 2/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.2939 - sparse_categorical_accuracy: 0.9141 - val_loss: 0.2592 - val_sparse_categorical_accuracy: 0.9238\n",
      "Epoch 3/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.2626 - sparse_categorical_accuracy: 0.9228 - val_loss: 0.2418 - val_sparse_categorical_accuracy: 0.9283\n",
      "Epoch 4/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.2443 - sparse_categorical_accuracy: 0.9287 - val_loss: 0.2302 - val_sparse_categorical_accuracy: 0.9307\n",
      "Epoch 5/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.2311 - sparse_categorical_accuracy: 0.9322 - val_loss: 0.2243 - val_sparse_categorical_accuracy: 0.9335\n",
      "Epoch 6/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.2218 - sparse_categorical_accuracy: 0.9341 - val_loss: 0.2165 - val_sparse_categorical_accuracy: 0.9346\n",
      "Epoch 7/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.2132 - sparse_categorical_accuracy: 0.9371 - val_loss: 0.2110 - val_sparse_categorical_accuracy: 0.9378\n",
      "Epoch 8/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.2073 - sparse_categorical_accuracy: 0.9382 - val_loss: 0.2071 - val_sparse_categorical_accuracy: 0.9368\n",
      "Epoch 9/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.2018 - sparse_categorical_accuracy: 0.9402 - val_loss: 0.2016 - val_sparse_categorical_accuracy: 0.9403\n",
      "Epoch 10/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1969 - sparse_categorical_accuracy: 0.9420 - val_loss: 0.2040 - val_sparse_categorical_accuracy: 0.9360\n",
      "Epoch 11/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1928 - sparse_categorical_accuracy: 0.9424 - val_loss: 0.1970 - val_sparse_categorical_accuracy: 0.9411\n",
      "Epoch 12/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1889 - sparse_categorical_accuracy: 0.9443 - val_loss: 0.1997 - val_sparse_categorical_accuracy: 0.9380\n",
      "Epoch 13/50\n",
      "60000/60000 [==============================] - 3s 58us/sample - loss: 0.1856 - sparse_categorical_accuracy: 0.9445 - val_loss: 0.1953 - val_sparse_categorical_accuracy: 0.9420\n",
      "Epoch 14/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.1827 - sparse_categorical_accuracy: 0.9462 - val_loss: 0.1867 - val_sparse_categorical_accuracy: 0.9430\n",
      "Epoch 15/50\n",
      "60000/60000 [==============================] - 3s 58us/sample - loss: 0.1800 - sparse_categorical_accuracy: 0.9464 - val_loss: 0.1874 - val_sparse_categorical_accuracy: 0.9420\n",
      "Epoch 16/50\n",
      "60000/60000 [==============================] - 3s 58us/sample - loss: 0.1779 - sparse_categorical_accuracy: 0.9466 - val_loss: 0.1875 - val_sparse_categorical_accuracy: 0.9435\n",
      "Epoch 17/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1755 - sparse_categorical_accuracy: 0.9477 - val_loss: 0.1878 - val_sparse_categorical_accuracy: 0.9432\n",
      "Epoch 18/50\n",
      "60000/60000 [==============================] - 3s 58us/sample - loss: 0.1741 - sparse_categorical_accuracy: 0.9473 - val_loss: 0.1890 - val_sparse_categorical_accuracy: 0.9440\n",
      "Epoch 19/50\n",
      "60000/60000 [==============================] - 4s 59us/sample - loss: 0.1716 - sparse_categorical_accuracy: 0.9488 - val_loss: 0.1880 - val_sparse_categorical_accuracy: 0.9412\n",
      "Epoch 20/50\n",
      "60000/60000 [==============================] - 3s 58us/sample - loss: 0.1699 - sparse_categorical_accuracy: 0.9493 - val_loss: 0.1838 - val_sparse_categorical_accuracy: 0.9441\n",
      "Epoch 21/50\n",
      "60000/60000 [==============================] - 4s 58us/sample - loss: 0.1684 - sparse_categorical_accuracy: 0.9494 - val_loss: 0.1797 - val_sparse_categorical_accuracy: 0.9446\n",
      "Epoch 22/50\n",
      "60000/60000 [==============================] - 3s 58us/sample - loss: 0.1671 - sparse_categorical_accuracy: 0.9499 - val_loss: 0.1786 - val_sparse_categorical_accuracy: 0.9439\n",
      "Epoch 23/50\n",
      "60000/60000 [==============================] - 4s 59us/sample - loss: 0.1651 - sparse_categorical_accuracy: 0.9505 - val_loss: 0.1847 - val_sparse_categorical_accuracy: 0.9440\n",
      "Epoch 24/50\n",
      "60000/60000 [==============================] - 3s 58us/sample - loss: 0.1645 - sparse_categorical_accuracy: 0.9506 - val_loss: 0.1829 - val_sparse_categorical_accuracy: 0.9443\n",
      "Epoch 25/50\n",
      "60000/60000 [==============================] - 4s 58us/sample - loss: 0.1626 - sparse_categorical_accuracy: 0.9510 - val_loss: 0.1856 - val_sparse_categorical_accuracy: 0.9420\n",
      "Epoch 26/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.1621 - sparse_categorical_accuracy: 0.9510 - val_loss: 0.1775 - val_sparse_categorical_accuracy: 0.9452\n",
      "Epoch 27/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1616 - sparse_categorical_accuracy: 0.9517 - val_loss: 0.1806 - val_sparse_categorical_accuracy: 0.9460\n",
      "Epoch 28/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1597 - sparse_categorical_accuracy: 0.9517 - val_loss: 0.1762 - val_sparse_categorical_accuracy: 0.9466\n",
      "Epoch 29/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.1586 - sparse_categorical_accuracy: 0.9516 - val_loss: 0.1793 - val_sparse_categorical_accuracy: 0.9426\n",
      "Epoch 30/50\n",
      "60000/60000 [==============================] - 4s 59us/sample - loss: 0.1584 - sparse_categorical_accuracy: 0.9520 - val_loss: 0.1755 - val_sparse_categorical_accuracy: 0.9465\n",
      "Epoch 31/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1570 - sparse_categorical_accuracy: 0.9520 - val_loss: 0.1797 - val_sparse_categorical_accuracy: 0.9452\n",
      "Epoch 32/50\n",
      "60000/60000 [==============================] - 3s 55us/sample - loss: 0.1558 - sparse_categorical_accuracy: 0.9526 - val_loss: 0.1759 - val_sparse_categorical_accuracy: 0.9443\n",
      "Epoch 33/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1552 - sparse_categorical_accuracy: 0.9524 - val_loss: 0.1784 - val_sparse_categorical_accuracy: 0.9433\n",
      "Epoch 34/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.1548 - sparse_categorical_accuracy: 0.9531 - val_loss: 0.1729 - val_sparse_categorical_accuracy: 0.9468\n",
      "Epoch 35/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.1535 - sparse_categorical_accuracy: 0.9528 - val_loss: 0.1762 - val_sparse_categorical_accuracy: 0.9455\n",
      "Epoch 36/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.1531 - sparse_categorical_accuracy: 0.9534 - val_loss: 0.1745 - val_sparse_categorical_accuracy: 0.9449\n",
      "Epoch 37/50\n",
      "60000/60000 [==============================] - 3s 55us/sample - loss: 0.1524 - sparse_categorical_accuracy: 0.9533 - val_loss: 0.1782 - val_sparse_categorical_accuracy: 0.9441\n",
      "Epoch 38/50\n",
      "60000/60000 [==============================] - 3s 54us/sample - loss: 0.1520 - sparse_categorical_accuracy: 0.9532 - val_loss: 0.1817 - val_sparse_categorical_accuracy: 0.9442\n",
      "Epoch 39/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1516 - sparse_categorical_accuracy: 0.9544 - val_loss: 0.1761 - val_sparse_categorical_accuracy: 0.9452\n",
      "Epoch 40/50\n",
      "60000/60000 [==============================] - 3s 54us/sample - loss: 0.1505 - sparse_categorical_accuracy: 0.9538 - val_loss: 0.1738 - val_sparse_categorical_accuracy: 0.9451\n",
      "Epoch 41/50\n",
      "60000/60000 [==============================] - 3s 55us/sample - loss: 0.1496 - sparse_categorical_accuracy: 0.9545 - val_loss: 0.1753 - val_sparse_categorical_accuracy: 0.9437\n",
      "Epoch 42/50\n",
      "60000/60000 [==============================] - 3s 54us/sample - loss: 0.1495 - sparse_categorical_accuracy: 0.9539 - val_loss: 0.1750 - val_sparse_categorical_accuracy: 0.9451\n",
      "Epoch 43/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.1492 - sparse_categorical_accuracy: 0.9542 - val_loss: 0.1811 - val_sparse_categorical_accuracy: 0.9454\n",
      "Epoch 44/50\n",
      "60000/60000 [==============================] - 3s 54us/sample - loss: 0.1481 - sparse_categorical_accuracy: 0.9546 - val_loss: 0.1803 - val_sparse_categorical_accuracy: 0.9440\n",
      "Epoch 45/50\n",
      "60000/60000 [==============================] - 3s 54us/sample - loss: 0.1475 - sparse_categorical_accuracy: 0.9546 - val_loss: 0.1743 - val_sparse_categorical_accuracy: 0.9451\n",
      "Epoch 46/50\n",
      "60000/60000 [==============================] - 3s 56us/sample - loss: 0.1482 - sparse_categorical_accuracy: 0.9543 - val_loss: 0.1749 - val_sparse_categorical_accuracy: 0.9463\n",
      "Epoch 47/50\n",
      "60000/60000 [==============================] - 3s 54us/sample - loss: 0.1465 - sparse_categorical_accuracy: 0.9554 - val_loss: 0.1768 - val_sparse_categorical_accuracy: 0.9444\n",
      "Epoch 48/50\n",
      "60000/60000 [==============================] - 3s 54us/sample - loss: 0.1464 - sparse_categorical_accuracy: 0.9550 - val_loss: 0.1779 - val_sparse_categorical_accuracy: 0.9465\n",
      "Epoch 49/50\n",
      "60000/60000 [==============================] - 3s 58us/sample - loss: 0.1458 - sparse_categorical_accuracy: 0.9552 - val_loss: 0.1731 - val_sparse_categorical_accuracy: 0.9471\n",
      "Epoch 50/50\n",
      "60000/60000 [==============================] - 3s 57us/sample - loss: 0.1453 - sparse_categorical_accuracy: 0.9558 - val_loss: 0.1803 - val_sparse_categorical_accuracy: 0.9455\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.callbacks.History at 0x15a4b2490>"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = SpatialGCN(W_sample)\n",
    "model.compile(optimizer, loss=loss_fn, metrics=metrics)\n",
    "model.fit(x_train_sample, y_train, epochs=50, batch_size=128, validation_data=(x_test_sample, y_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 可视化第二个avg pooling的聚类结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [],
   "source": [
    "node2clusterid = model.ap_800.node2cluster_matrix.dot(model.ap_400.node2cluster_matrix)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [],
   "source": [
    "def draw_cluster_result(node2clusterid):\n",
    "    kernel_img = np.zeros((img_width, img_width), dtype=np.float32)\n",
    "    for i in range(len(sample_inds)):\n",
    "        ind = sample_inds[i]\n",
    "        row = ind//img_width\n",
    "        col = ind%img_width\n",
    "        cluster_id = np.argmax(node2clusterid[i, :])+1\n",
    "        kernel_img[row, col] = cluster_id\n",
    "        sns.heatmap(kernel_img, mask=mask, cmap=sns.color_palette(\"Paired\"), xticklabels=False, yticklabels=False, cbar=False, square=True).set_title(\"cluster result with cluster number %s\" % node2clusterid.shape[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "draw_cluster_result(node2clusterid)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 60000 samples, validate on 10000 samples\n",
      "Epoch 1/50\n",
      "60000/60000 [==============================] - 2s 37us/sample - loss: 0.4600 - sparse_categorical_accuracy: 0.8676 - val_loss: 0.3302 - val_sparse_categorical_accuracy: 0.9052\n",
      "Epoch 2/50\n",
      "60000/60000 [==============================] - 2s 30us/sample - loss: 0.3244 - sparse_categorical_accuracy: 0.9059 - val_loss: 0.2984 - val_sparse_categorical_accuracy: 0.9153\n",
      "Epoch 3/50\n",
      "60000/60000 [==============================] - 2s 31us/sample - loss: 0.2971 - sparse_categorical_accuracy: 0.9146 - val_loss: 0.2825 - val_sparse_categorical_accuracy: 0.9183\n",
      "Epoch 4/50\n",
      "60000/60000 [==============================] - 2s 31us/sample - loss: 0.2808 - sparse_categorical_accuracy: 0.9183 - val_loss: 0.2738 - val_sparse_categorical_accuracy: 0.9187\n",
      "Epoch 5/50\n",
      "60000/60000 [==============================] - 2s 31us/sample - loss: 0.2703 - sparse_categorical_accuracy: 0.9213 - val_loss: 0.2668 - val_sparse_categorical_accuracy: 0.9226\n",
      "Epoch 6/50\n",
      "60000/60000 [==============================] - 2s 30us/sample - loss: 0.2617 - sparse_categorical_accuracy: 0.9237 - val_loss: 0.2616 - val_sparse_categorical_accuracy: 0.9235\n",
      "Epoch 7/50\n",
      "60000/60000 [==============================] - 2s 29us/sample - loss: 0.2551 - sparse_categorical_accuracy: 0.9255 - val_loss: 0.2563 - val_sparse_categorical_accuracy: 0.9243\n",
      "Epoch 8/50\n",
      "60000/60000 [==============================] - 2s 31us/sample - loss: 0.2495 - sparse_categorical_accuracy: 0.9263 - val_loss: 0.2521 - val_sparse_categorical_accuracy: 0.9248\n",
      "Epoch 9/50\n",
      "60000/60000 [==============================] - 2s 30us/sample - loss: 0.2450 - sparse_categorical_accuracy: 0.9280 - val_loss: 0.2488 - val_sparse_categorical_accuracy: 0.9249\n",
      "Epoch 10/50\n",
      "60000/60000 [==============================] - 2s 30us/sample - loss: 0.2406 - sparse_categorical_accuracy: 0.9288 - val_loss: 0.2477 - val_sparse_categorical_accuracy: 0.9266\n",
      "Epoch 11/50\n",
      "60000/60000 [==============================] - 2s 31us/sample - loss: 0.2371 - sparse_categorical_accuracy: 0.9302 - val_loss: 0.2445 - val_sparse_categorical_accuracy: 0.9278\n",
      "Epoch 12/50\n",
      "60000/60000 [==============================] - 2s 31us/sample - loss: 0.2341 - sparse_categorical_accuracy: 0.9307 - val_loss: 0.2433 - val_sparse_categorical_accuracy: 0.9277\n",
      "Epoch 13/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.2312 - sparse_categorical_accuracy: 0.9316 - val_loss: 0.2424 - val_sparse_categorical_accuracy: 0.9284\n",
      "Epoch 14/50\n",
      "60000/60000 [==============================] - 2s 32us/sample - loss: 0.2289 - sparse_categorical_accuracy: 0.9323 - val_loss: 0.2411 - val_sparse_categorical_accuracy: 0.9292\n",
      "Epoch 15/50\n",
      "60000/60000 [==============================] - 2s 32us/sample - loss: 0.2269 - sparse_categorical_accuracy: 0.9327 - val_loss: 0.2393 - val_sparse_categorical_accuracy: 0.9289\n",
      "Epoch 16/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.2247 - sparse_categorical_accuracy: 0.9332 - val_loss: 0.2389 - val_sparse_categorical_accuracy: 0.9286\n",
      "Epoch 17/50\n",
      "60000/60000 [==============================] - 2s 32us/sample - loss: 0.2225 - sparse_categorical_accuracy: 0.9339 - val_loss: 0.2372 - val_sparse_categorical_accuracy: 0.9314\n",
      "Epoch 18/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.2211 - sparse_categorical_accuracy: 0.9344 - val_loss: 0.2361 - val_sparse_categorical_accuracy: 0.9304\n",
      "Epoch 19/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2195 - sparse_categorical_accuracy: 0.9343 - val_loss: 0.2343 - val_sparse_categorical_accuracy: 0.9309\n",
      "Epoch 20/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2183 - sparse_categorical_accuracy: 0.9349 - val_loss: 0.2332 - val_sparse_categorical_accuracy: 0.9310\n",
      "Epoch 21/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2165 - sparse_categorical_accuracy: 0.9355 - val_loss: 0.2332 - val_sparse_categorical_accuracy: 0.9315\n",
      "Epoch 22/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2152 - sparse_categorical_accuracy: 0.9353 - val_loss: 0.2358 - val_sparse_categorical_accuracy: 0.9301\n",
      "Epoch 23/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.2144 - sparse_categorical_accuracy: 0.9358 - val_loss: 0.2320 - val_sparse_categorical_accuracy: 0.9320\n",
      "Epoch 24/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.2132 - sparse_categorical_accuracy: 0.9364 - val_loss: 0.2322 - val_sparse_categorical_accuracy: 0.9321\n",
      "Epoch 25/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2119 - sparse_categorical_accuracy: 0.9367 - val_loss: 0.2328 - val_sparse_categorical_accuracy: 0.9329\n",
      "Epoch 26/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2109 - sparse_categorical_accuracy: 0.9366 - val_loss: 0.2313 - val_sparse_categorical_accuracy: 0.9327\n",
      "Epoch 27/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2103 - sparse_categorical_accuracy: 0.9366 - val_loss: 0.2295 - val_sparse_categorical_accuracy: 0.9319\n",
      "Epoch 28/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2092 - sparse_categorical_accuracy: 0.9378 - val_loss: 0.2330 - val_sparse_categorical_accuracy: 0.9308\n",
      "Epoch 29/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2085 - sparse_categorical_accuracy: 0.9377 - val_loss: 0.2302 - val_sparse_categorical_accuracy: 0.9326\n",
      "Epoch 30/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2075 - sparse_categorical_accuracy: 0.9377 - val_loss: 0.2306 - val_sparse_categorical_accuracy: 0.9321\n",
      "Epoch 31/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2072 - sparse_categorical_accuracy: 0.9378 - val_loss: 0.2296 - val_sparse_categorical_accuracy: 0.9328\n",
      "Epoch 32/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2062 - sparse_categorical_accuracy: 0.9380 - val_loss: 0.2287 - val_sparse_categorical_accuracy: 0.9330\n",
      "Epoch 33/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2056 - sparse_categorical_accuracy: 0.9388 - val_loss: 0.2291 - val_sparse_categorical_accuracy: 0.9322\n",
      "Epoch 34/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2049 - sparse_categorical_accuracy: 0.9388 - val_loss: 0.2285 - val_sparse_categorical_accuracy: 0.9329\n",
      "Epoch 35/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2042 - sparse_categorical_accuracy: 0.9393 - val_loss: 0.2280 - val_sparse_categorical_accuracy: 0.9329\n",
      "Epoch 36/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2037 - sparse_categorical_accuracy: 0.9386 - val_loss: 0.2295 - val_sparse_categorical_accuracy: 0.9320\n",
      "Epoch 37/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2031 - sparse_categorical_accuracy: 0.9393 - val_loss: 0.2295 - val_sparse_categorical_accuracy: 0.9327\n",
      "Epoch 38/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2024 - sparse_categorical_accuracy: 0.9399 - val_loss: 0.2285 - val_sparse_categorical_accuracy: 0.9314\n",
      "Epoch 39/50\n",
      "60000/60000 [==============================] - 2s 35us/sample - loss: 0.2018 - sparse_categorical_accuracy: 0.9395 - val_loss: 0.2315 - val_sparse_categorical_accuracy: 0.9318\n",
      "Epoch 40/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2014 - sparse_categorical_accuracy: 0.9394 - val_loss: 0.2299 - val_sparse_categorical_accuracy: 0.9314\n",
      "Epoch 41/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.2005 - sparse_categorical_accuracy: 0.9404 - val_loss: 0.2306 - val_sparse_categorical_accuracy: 0.9319\n",
      "Epoch 42/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.2006 - sparse_categorical_accuracy: 0.9399 - val_loss: 0.2311 - val_sparse_categorical_accuracy: 0.9320\n",
      "Epoch 43/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.2000 - sparse_categorical_accuracy: 0.9399 - val_loss: 0.2271 - val_sparse_categorical_accuracy: 0.9340\n",
      "Epoch 44/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.1994 - sparse_categorical_accuracy: 0.9403 - val_loss: 0.2271 - val_sparse_categorical_accuracy: 0.9335\n",
      "Epoch 45/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.1989 - sparse_categorical_accuracy: 0.9406 - val_loss: 0.2277 - val_sparse_categorical_accuracy: 0.9328\n",
      "Epoch 46/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.1985 - sparse_categorical_accuracy: 0.9399 - val_loss: 0.2294 - val_sparse_categorical_accuracy: 0.9325\n",
      "Epoch 47/50\n",
      "60000/60000 [==============================] - 2s 34us/sample - loss: 0.1981 - sparse_categorical_accuracy: 0.9403 - val_loss: 0.2273 - val_sparse_categorical_accuracy: 0.9334\n",
      "Epoch 48/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.1975 - sparse_categorical_accuracy: 0.9401 - val_loss: 0.2296 - val_sparse_categorical_accuracy: 0.9333\n",
      "Epoch 49/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.1974 - sparse_categorical_accuracy: 0.9405 - val_loss: 0.2286 - val_sparse_categorical_accuracy: 0.9323\n",
      "Epoch 50/50\n",
      "60000/60000 [==============================] - 2s 33us/sample - loss: 0.1973 - sparse_categorical_accuracy: 0.9406 - val_loss: 0.2313 - val_sparse_categorical_accuracy: 0.9317\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.callbacks.History at 0x1581bb7d0>"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = SpectralGCN(W_sample)\n",
    "model.compile(optimizer, loss=loss_fn, metrics=metrics)\n",
    "model.fit(x_train_sample, y_train, epochs=50, batch_size=128, validation_data=(x_test_sample, y_test))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
